using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace TyranntClient
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class TyranntGame : Microsoft.Xna.Framework.Game
    {

        KeyboardState keyboardState;
        KeyboardState previousKeyBoardState;

        Vector3 cameraPosition;
        Vector3 cameraPositionOffset;
        Vector3 cameraTarget;
        Vector3 moveToPosition = Vector3.Zero;
        Vector3 moveToSpeed = Vector3.Zero;
        float heading = 90f;
        float headingTarget = 0;

        float moveSpeed;
        float turnStep = 90f;
        static float turnSpeed = 5f;
        float turnDirection = 1f;
        static int blockSize = 10;

        float xMove;
        float zMove;

        Texture2D[] skyboxTextures;
        Model skyboxModel;

        Matrix viewMatrix;
        Matrix perspectiveMatrix;
        float aspectRatio;

        GraphicsDeviceManager graphics;
        GraphicsDevice device;
        SpriteBatch spriteBatch;
        Effect effect;

        MazeBlock[] maze;
        static int mazeWidth = 10;
        static int mazeHeight = 10;
        int currentBlock = 0;
        MazeBlock presentBlock;

        SpriteFont font;

        GameLog gameLog;

        int gameState = 0;
        static int gsMenu = 0;      //In menu
        static int gsPlayer = 1;    //Player's turn
        static int gsEnemy = 2;     //Enemy's turn
        static int gsMoving = 3;    //Player is moving
        static int gsTurning = 4;   //Player is turning
        static int gsGameOver = 5;  //Game over

        Texture2D map;
        Vector2 mapPosition;

        Random random;

        public TyranntGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();

        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            device = graphics.GraphicsDevice;
            spriteBatch = new SpriteBatch(device);
            effect = Content.Load<Effect>("Effects\\effects");

            font = Content.Load<SpriteFont>("Fonts\\GameFont");

            random = new Random();

            cameraPosition = new Vector3(blockSize, blockSize / 1.75f, blockSize);
            cameraTarget = new Vector3(blockSize, blockSize / 1.75f, blockSize);

            aspectRatio = (float) device.Viewport.Width / (float)device.Viewport.Height;
            viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up);
            perspectiveMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 50000.0f);

            Model floorModel = Content.Load<Model>("Models\\floor");
            Model hedgeModel = Content.Load<Model>("Models\\hedge");
            Model hedgeDoorModel = Content.Load<Model>("Models\\hedgeDoor");
            Model creatureModel = Content.Load<Model>("Models\\test");
            Model lichModel = Content.Load<Model>("Models\\test");

            skyboxModel = LoadModel("Models\\skybox", out skyboxTextures);

            maze = new MazeBlock[(mazeHeight * mazeWidth)];

            int x = 1;
            int z = 1;

            //Create outer maze boundaries
            for (int i = 0; i < mazeHeight * mazeWidth; i++)
            {
                bool d = true;
                bool n = d;
                bool e = d;
                bool s = d;
                bool w = d;

                if (x == 1) { w = false; }
                else if (x == mazeWidth) { e = false; }
                if (z == 1) { s = false; }
                else if (z == mazeHeight) { n = false; }
                maze[i] = new MazeBlock(n, e, s, w, new Vector3(x, 0, z), blockSize);
                maze[i].floorModel = floorModel;
                maze[i].hedgeModel = hedgeModel;
                maze[i].hedgeDoorModel = hedgeDoorModel;
                x += 1;
                if (x > mazeWidth) { x = 1; z += 1; }

            }

            //Create inner maze
            for (int i = 0; i < 100; i++)
            {
                int xx = 1 + random.Next(mazeWidth - 2);
                int zz = 1 + random.Next(mazeHeight - 2);
                int rr = random.Next(4);
                if (rr == 1 || rr == 2)
                {
                    int block = xx + zz * mazeWidth;
                    maze[block].east = false;
                    block = xx + 1 + zz * mazeWidth;
                    maze[block].west = false;
                    if (rr == 2)
                    {
                        maze[block].door = true;
                    }
                }
                rr = random.Next(4);
                if (rr == 1 || rr == 2)
                {
                    int block = xx + zz * mazeWidth;
                    maze[block].north = false;
                    block = xx + (zz + 1) * mazeWidth;
                    maze[block].south = false;
                    if (rr == 2)
                    {
                        maze[block].door = true;
                    }
                }
            }

            //Evaluate current position
            currentBlock = CalcBlockFromWorld(cameraPosition.X, cameraPosition.Y);

            previousKeyBoardState = Keyboard.GetState();
            moveSpeed = blockSize;
            gameLog = new GameLog(new Vector2(60f, 10f), Color.White);
            gameLog.NewLine("Welcome");

            //Create map texture
            gameState = gsMenu;
            map = new Texture2D(device, 32, 32, 0, TextureUsage.Linear, SurfaceFormat.Color);
            BuildMap();

        }

        //Create the map texture
        void BuildMap()
        {

            Color mapBGColor = Color.TransparentBlack;
            Color mapFGColor = Color.DarkGray;
            Color mapSColor = Color.Blue;
            Color[] pixels = new Color[map.Width * map.Height];
            //Erase the map
            for (int my = 0; my < map.Height; my++)
            {
                for (int mx = 0; mx < map.Width; mx++)
                {
                    pixels[my * map.Width + mx] = mapBGColor;
                }
            }

            //Draw the map
            int xcount = 0;
            int ycount = 0;
            for (int i = 0; i < mazeHeight * mazeWidth; i++)
            {
                pixels[(ycount + 1) * map.Width + xcount + 1] = mapFGColor; //mark the center of the block
                if (maze[i].north) { pixels[(ycount + 2) * map.Width + xcount + 1] = mapFGColor; }
                if (maze[i].south) { pixels[(ycount) * map.Width + xcount + 1] = mapFGColor; }
                if (maze[i].east) { pixels[(ycount + 1) * map.Width + xcount + 2] = mapFGColor; }
                if (maze[i].west) { pixels[(ycount + 1) * map.Width + xcount] = mapFGColor; }
                if (maze[i].occupied) { pixels[(ycount + 1) * map.Width + xcount + 1] = mapSColor; }
                if (i == currentBlock) { pixels[(ycount + 1) * map.Width + xcount + 1] = Color.Blue; }
                xcount += 3;
                if (xcount == mazeWidth * 3)
                {
                    xcount = 0;
                    ycount += 3;
                }
            }

            //set the map
            map.SetData<Color>(pixels);
            mapPosition = new Vector2(0, device.Viewport.Height - map.Height * 3);
        }

        private Model LoadModel(string assetName, out Texture2D[] textures)
        {

            Model newModel = Content.Load<Model>(assetName);
            textures = new Texture2D[newModel.Meshes.Count];
            int i = 0;
            foreach (ModelMesh mesh in newModel.Meshes)
                foreach (BasicEffect currentEffect in mesh.Effects)
                    textures[i++] = currentEffect.Texture;

            foreach (ModelMesh mesh in newModel.Meshes)
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                    meshPart.Effect = effect.Clone(device);

            return newModel;
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            keyboardState = Keyboard.GetState();

            xMove = (float) Math.Sin(MathHelper.ToRadians(heading)) * moveSpeed;
            zMove = (float) Math.Cos(MathHelper.ToRadians(heading)) * moveSpeed;

            presentBlock = maze[(CalcBlockFromWorld(cameraPosition.X, cameraPosition.Z))];
            UpdateBlockPosition();

            if (gameState == gsPlayer)      //Player's turn
            {
                //Move player
                CheckPlayerMove();

                //Handle any actions
                PlayerAction();
            }
            else if (gameState == gsMoving)     //Player is moving
            {
                PlayerMoving();
            }
            else if (gameState == gsTurning)    //Player is turning around
            {
                PlayerTurning();
            }
            if (gameState == gsEnemy)      //Enemy's turn
            {
                moveEnemy();
            }
            else if (gameState == gsMenu)   //In Menu
            {
                ShowMenu();
            }
            else if (gameState == gsGameOver)   //Game over
            {
                ShowGameOver();
            }

            //Update camera position
            UpdateCamera();


            previousKeyBoardState = keyboardState;

            base.Update(gameTime);
        }

        //Display the main menu
        void ShowMenu()
        {
            if (keyboardState.IsKeyDown(Keys.Space)) { gameState = gsPlayer; }
        }

        //Show the game over screen
        void ShowGameOver()
        {
            if (keyboardState.IsKeyDown(Keys.Space)) { this.Initialize(); }
        }

        //Update player moving block
        void PlayerMoving()
        {
            cameraPosition += moveToSpeed;
            if (cameraPosition == moveToPosition) 
            { 
                gameState = gsEnemy;    //Enemy's turn now
            }
        }

        //Handle the player's action
        void PlayerAction()
        {
            BuildMap();
        }

        //Update player turning left or right
        void PlayerTurning()
        {
            heading += turnSpeed * turnDirection;

            if (heading == headingTarget)
            {
                gameState = gsPlayer;
                if (heading >= 360) { heading = heading - 360; }
                else if (heading < 0) { heading = 360 + heading; }
            }

        }

        //Check if player wants to move or turn around
        void CheckPlayerMove()
        {

            if (keyboardState.IsKeyDown(Keys.Right) && previousKeyBoardState.IsKeyUp(Keys.Right))   //Turn right
            {
                headingTarget = heading - turnStep;
                turnDirection = -1;
                gameState = gsTurning;
            }
            else if (keyboardState.IsKeyDown(Keys.Left) && previousKeyBoardState.IsKeyUp(Keys.Left))    //Turn left
            {
                headingTarget = heading + turnStep;
                turnDirection = 1;
                gameState = gsTurning;
            }


            if (keyboardState.IsKeyDown(Keys.Up) && previousKeyBoardState.IsKeyUp(Keys.Up))     //Move forward
            {
                //Check if we can move forward
                bool moveOkay = CalcWallInFront(heading, presentBlock);
                if (moveOkay)
                {
                    moveToSpeed = Vector3.Normalize(new Vector3(xMove, 0, zMove));
                    moveToPosition = cameraPosition + new Vector3((float)Math.Round(xMove, 0), 0, (float)Math.Round(zMove, 0));
                    gameState = gsMoving;
                }

            }

        }

        //Move enemy
        void moveEnemy()
        {
            gameState = gsPlayer;
        }

        //Checks if the player can move forward
        bool CalcWallInFront(float h, MazeBlock c)
        {
            bool f = false;
            if (heading == 0 && c.north) { f = true; } //north
            else if (heading == 90 && c.east) { f = true; } //east
            else if (heading == 180 && c.south) { f = true; } //west
            else if (heading == 270 && c.west) { f = true; }
            return f;
        }

        //Update the position of the camera
        void UpdateCamera()
        {
            double hR = MathHelper.ToRadians(heading);
            cameraTarget = new Vector3(cameraPosition.X + xMove, cameraTarget.Y, cameraPosition.Z + zMove);
            cameraPositionOffset = new Vector3(-xMove * 0.5f, -blockSize * 0.25f, -zMove * 0.5f);
            Vector3 cameraTargetOffset = new Vector3(xMove, 0, zMove);
            viewMatrix = Matrix.CreateLookAt(cameraPosition + cameraPositionOffset, cameraTarget + cameraTargetOffset * 1.5f, Vector3.Up);
        }

        //Update the current position inside the maze
        void UpdateBlockPosition()
        {
            int oldBlock = currentBlock;
            currentBlock = CalcBlockFromWorld(cameraPosition.X, cameraPosition.Z);
            int dASize = maze.GetUpperBound(0);
            if (oldBlock != currentBlock && currentBlock < dASize && currentBlock >= 0 && oldBlock < dASize && oldBlock >= 0)
            {
                maze[oldBlock].activeBlock = false;
                maze[currentBlock].activeBlock = true;
            }
        }

        //Obtain the block from a world coordinate
        int CalcBlockFromWorld(float checkX, float checkZ)
        {
            int newBlock = 0;
            int xPos = (int)((checkX - blockSize / 2) / blockSize);
            int zPos = (int)((checkZ - blockSize / 2) / blockSize);
            zPos = (zPos) * mazeWidth;
            newBlock = xPos + zPos;
            return newBlock;
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            device.Clear(Color.Black);
            device.RenderState.DepthBufferEnable = true;

            //Enable texture-transparency
            device.RenderState.AlphaBlendEnable = true; 
            device.RenderState.SourceBlend = Blend.SourceAlpha;
            device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

            device.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            device.SamplerStates[0].AddressV = TextureAddressMode.Wrap;

            if (gameState != gsMenu)
            {
                //Draw skybox
                DrawSkybox();

                //Draw maze
                foreach (MazeBlock d in maze)
                {
                    d.Draw(viewMatrix, perspectiveMatrix, heading);
                }

                spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                //Draw score board
                spriteBatch.DrawString(font, "Gold: 0", new Vector2(device.Viewport.Width - 50, 10), Color.Yellow);

                //Draw map
                spriteBatch.Draw(map, new Rectangle((int)mapPosition.X, (int)mapPosition.Y, map.Width * 3, map.Height * 3), Color.White);
                DrawGameLog();
                spriteBatch.End();
            }
            else if (gameState == gsMenu)
            {
                spriteBatch.Begin(SpriteBlendMode.Additive);
                spriteBatch.DrawString(font, "Press SPACE to start the game", new Vector2(device.Viewport.Width / 2 - 100, device.Viewport.Height / 2), Color.Green);
                spriteBatch.End();
            }

            if (gameState == gsGameOver)
            {
                spriteBatch.Begin(SpriteBlendMode.Additive);
                spriteBatch.DrawString(font, "Game over! Press SPACE to Restart.", new Vector2(device.Viewport.Width / 2 - 100, device.Viewport.Height / 2), Color.Green);
                spriteBatch.End();
            }
            base.Draw(gameTime);
        }

        //Draw the skybox
        private void DrawSkybox()
        {
            device.SamplerStates[0].AddressU = TextureAddressMode.Clamp;
            device.SamplerStates[0].AddressV = TextureAddressMode.Clamp;

            device.RenderState.DepthBufferWriteEnable = false;
            Matrix[] skyboxTransforms = new Matrix[skyboxModel.Bones.Count];
            skyboxModel.CopyAbsoluteBoneTransformsTo(skyboxTransforms);
            int i = 0;
            foreach (ModelMesh mesh in skyboxModel.Meshes)
            {
                foreach (Effect currentEffect in mesh.Effects)
                {
                    Matrix worldMatrix = skyboxTransforms[mesh.ParentBone.Index] * Matrix.CreateTranslation(cameraTarget);
                    currentEffect.CurrentTechnique = currentEffect.Techniques["Textured"];
                    currentEffect.Parameters["xWorld"].SetValue(worldMatrix);
                    currentEffect.Parameters["xView"].SetValue(viewMatrix);
                    currentEffect.Parameters["xProjection"].SetValue(perspectiveMatrix);
                    currentEffect.Parameters["xTexture"].SetValue(skyboxTextures[i++]);
                }
                mesh.Draw();
            }
            device.RenderState.DepthBufferWriteEnable = true;
        }

        //Draw the game log
        void DrawGameLog()
        {
            if (gameLog.active)
            {
                int counter = 0;
                foreach (string message in gameLog.eventList)
                {
                    spriteBatch.DrawString(font, message, new Vector2(gameLog.position.X, gameLog.position.Y + counter * 20), Color.Yellow);
                    counter += 1;
                }
            }
        }

        /*
        void DrawModelTest(Model model)
        {

            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.PreferPerPixelLighting = true;
                    effect.World = Matrix.CreateFromYawPitchRoll(0, 0, 0) *
                        Matrix.CreateScale(1.0f) * Matrix.CreateTranslation(new Vector3(0, 0, 10));
                    //effect.World = Matrix.CreateTranslation(gameObject.position);
                    effect.Projection = perspectiveMatrix;
                    effect.View = viewMatrix;
                }
                mesh.Draw();
            }

        }
         */
    }
}
